#include "elmessage.h"
#include <QPaintEvent>
#include <QPainter>
#include <QPropertyAnimation>
#include <QTimer>
#include "../basic/elbutton.h"

const int ElMessage::Min_Width = 250;
const int ElMessage::Icon_Size = 16;
const int ElMessage::Spacing = 5;
const int ElMessage::Padding_Lr = 15;
const int ElMessage::Padding_Tb = 8;
const int ElMessage::Init_Offset = 15;
QList<ElMessage *> ElMessage::msg_manager;

ElMessage::ElMessage(QWidget *parent)
    : ElComponent{parent}
{
    _icon = new ElIcon(":/icons/info.svg", this);

    _fadeTimer = new QTimer(this);
    _fadeTimer->setTimerType(Qt::CoarseTimer);
    _fadeTimer->setSingleShot(true);
    connect(_fadeTimer, &QTimer::timeout, this, [=] {
        _is_repeat_click ^= 1;
        manager_handle_msgs_animation();
    });

    initAnimation();
    initPalette();
    hide();

    msg_manager << this;
    _no = msg_manager.size() - 1; // get the index
}

ElMessage::ElMessage(QWidget *parent,
                     Type type,
                     const QString &msg,
                     bool isClosable,
                     bool isCenter,
                     int duration,
                     int offset)
    : ElMessage{parent}
{
    setType(type);
    setMessage(msg);
    setClosable(isClosable);
    setCenter(isCenter);
    setDuration(duration);
    setOffset(offset);
}

void ElMessage::adjustifyAllPos()
{
    for (auto &msg : msg_manager) {
        auto parent = dynamic_cast<QWidget *>(msg->parent());
        msg->move((parent->width() - msg->width()) / 2,
                  msg->_offset + msg->_no * (msg->height() + msg->_offset));
    }
}

void ElMessage::success(
    QWidget *parent, const QString &msg, bool isClosable, bool isCenter, int duration, int offset)
{
    auto msg_box = new ElMessage(parent, Success, msg, isClosable, isCenter, duration, offset);
    msg_box->show();
}

void ElMessage::warning(
    QWidget *parent, const QString &msg, bool isClosable, bool isCenter, int duration, int offset)
{
    auto msg_box = new ElMessage(parent, Warning, msg, isClosable, isCenter, duration, offset);
    msg_box->show();
}

void ElMessage::information(
    QWidget *parent, const QString &msg, bool isClosable, bool isCenter, int duration, int offset)
{
    auto msg_box = new ElMessage(parent, Info, msg, isClosable, isCenter, duration, offset);
    msg_box->show();
}

void ElMessage::error(
    QWidget *parent, const QString &msg, bool isClosable, bool isCenter, int duration, int offset)
{
    auto msg_box = new ElMessage(parent, Error, msg, isClosable, isCenter, duration, offset);
    msg_box->show();
}

QString ElMessage::message() const
{
    return _msg;
}

void ElMessage::setMessage(const QString &message)
{
    _msg = message;
}

ElMessage::Type ElMessage::type() const
{
    switch (_theme.type()) {
    case ElTheme::Success:
        return Success;
    case ElTheme::Warning:
        return Warning;
    case ElTheme::Information:
        return Info;
    case ElTheme::Danger:
        return Error;
    default:
        return Info;
    }
}

void ElMessage::setType(Type type)
{
    switch (type) {
    case Success:
        _theme.setType(ElTheme::Success);
        _bdColor = QColor(225, 243, 216);
        setIcon(":/icons/success.svg");
        break;
    case Warning:
        _theme.setType(ElTheme::Warning);
        _bdColor = QColor(250, 236, 216);
        setIcon(":/icons/warning.svg");
        break;
    case Info:
        _theme.setType(ElTheme::Information);
        _bdColor = QColor(235, 238, 245);
        setIcon(":/icons/info.svg");
        break;
    case Error:
        _theme.setType(ElTheme::Danger);
        _bdColor = QColor(253, 226, 226);
        setIcon(":/icons/error.svg");
        break;
    default:
        break;
    }
    initPalette();
    update();
}

ElIcon *ElMessage::icon() const
{
    return _icon;
}

void ElMessage::setIcon(ElIcon *icon)
{
    if (icon == _icon)
        return;
    delete _icon;
    _icon = icon;
    _icon->resize(Icon_Size, Icon_Size);
}

void ElMessage::setIcon(const QString &fileName)
{
    delete _icon;
    _icon = new ElIcon(fileName, this);
    _icon->resize(Icon_Size, Icon_Size);
}

int ElMessage::duration() const
{
    return _duration;
}

void ElMessage::setDuration(int duration)
{
    _duration = duration;
}

int ElMessage::offset() const
{
    return _offset;
}

void ElMessage::setOffset(int offset)
{
    _offset = offset;
}

bool ElMessage::isCenter() const
{
    return _center;
}

void ElMessage::setCenter(bool center)
{
    _center = center;
}

bool ElMessage::isClosable() const
{
    return _closable;
}

void ElMessage::setClosable(bool closable)
{
    if (_closable == closable)
        return;
    _closable = closable;
    if (_closable) {
        createCloseBtn();
    } else {
        delete _closeBtn;
        _closeBtn = nullptr;
    }
    update();
}

void ElMessage::slideUp(int distance)
{
    refreshPosAnim(y() - distance - _offset - height(), y() - distance);
    _anim_pos->setDirection(QAbstractAnimation::Backward);
    if (_anim_pos->state() == QAbstractAnimation::Running)
        _anim_pos->stop();
    _anim_pos->start();
}

void ElMessage::fadeIn()
{
    repaint(); // update the size() or size would be wrong

    // due to uncertainty of _anim_pos's start and end value
    refreshPosAnim(-height() + _no * (height() + _offset), _offset + _no * (height() + _offset));
    _anim_pos->setDirection(QAbstractAnimation::Forward);
    if (_anim_pos->state() == QAbstractAnimation::Running)
        _anim_pos->stop();
    _anim_pos->start();

    _anim_opa->setDirection(QAbstractAnimation::Forward);
    if (_anim_opa->state() == QAbstractAnimation::Running)
        _anim_opa->stop();
    _anim_opa->start();

    startIconAnimation();
}

void ElMessage::fadeOut()
{
    startIconAnimation(false);

    _anim_opa->setDirection(QAbstractAnimation::Backward);
    if (_anim_opa->state() == QAbstractAnimation::Running)
        _anim_opa->stop();
    _anim_opa->start();

    connect(_anim_pos, &QPropertyAnimation::finished, this, &ElMessage::deleteLater);
    refreshPosAnim(-height() + _no * (height() + _offset), _offset + _no * (height() + _offset));
    _anim_pos->setDirection(QAbstractAnimation::Backward);
    if (_anim_pos->state() == QAbstractAnimation::Running)
        _anim_pos->stop();
    _anim_pos->start();
}

void ElMessage::paintEvent(QPaintEvent *e)
{
    QPainter painter(this);
    QRectF br;
    // use QRect() to draw text because we don't care about the text at all.
    painter.drawText(QRect(), Qt::AlignVCenter | Qt::AlignLeft, _msg, &br);
    auto w = br.width() + 2 * Padding_Lr + Icon_Size + Spacing;
    int extra_space = 0;
    if (w < Min_Width) {
        extra_space = Min_Width - w;
        w = Min_Width;
    }
    resize(w, br.height() + 2 * Padding_Tb);
    // draw rect
    QPen pen(_palette.color(ElPalette::Border));
    pen.setWidthF(1.5);
    painter.setPen(pen);
    painter.setBrush(_palette.color(ElPalette::Background));
    painter.drawRoundedRect(getDrawRect(), height() / 5.0, height() / 5.0);
    // show icon
    _icon->resize(QSize(Icon_Size, Icon_Size));
    QRect drawing_rect;
    if (_center && extra_space > 0) {
        // Only extra_space > 0 should set a centering effect manually.
        _icon->move(Padding_Lr + extra_space / 2, (height() - Icon_Size) / 2);
        drawing_rect = QRect(Padding_Lr + Icon_Size + Spacing + extra_space / 2,
                             0,
                             width() - 2 * Padding_Lr - Spacing - extra_space / 2,
                             height());
    } else {
        _icon->move(Padding_Lr, (height() - Icon_Size) / 2);
        drawing_rect = QRect(Padding_Lr + Icon_Size + Spacing,
                             0,
                             width() - 2 * Padding_Lr - Spacing,
                             height());
    }
    _icon->show();
    // draw text
    painter.setPen(_palette.color(ElPalette::Foreground));
    // a centering effect doesn't mean we need AlignHCenter when drawing text.
    painter.drawText(drawing_rect, Qt::AlignVCenter | Qt::AlignLeft, _msg);
    if (_closable) {
        // draw close button
        _closeBtn->move(width() - _closeBtn->width() - 5, (height() - _closeBtn->height()) / 2);
        _closeBtn->show();
    }

    e->accept();
}

void ElMessage::showEvent(QShowEvent *e)
{
    fadeIn();
    if (_duration > 0) // _duration == 0 will never fade out automatically.
        _fadeTimer->start(_duration);
    e->accept();
}

void ElMessage::initPalette()
{
    _palette.setColor(ElPalette::Background, _theme.shallowColor());
    _palette.setColor(ElPalette::Foreground, _theme.normalColor());
    _palette.setColor(ElPalette::Border, _bdColor);
}

void ElMessage::manager_handle_msgs_animation()
{
    fadeOut();
    // then remove it because it should be deleted
    msg_manager.remove(_no); // remember that _no is the next one after remove
    // the msgs after the index should slide up
    for (qsizetype i = _no; i < msg_manager.size(); ++i) {
        auto &msg = msg_manager[i];
        msg->refreshPosAnim(-msg->height() + msg->_no * (msg->height() + msg->_offset),
                            msg->_offset + msg->_no * (msg->height() + msg->_offset));
        msg->_anim_pos->setDirection(QAbstractAnimation::Backward);
        msg->_anim_pos->start();
        msg->_no = i; // must refresh animation first and then update msg's _no
    }
}

void ElMessage::initAnimation()
{
    _anim_pos = new QPropertyAnimation(this, "pos", this);
    _anim_pos->setEasingCurve(QEasingCurve::OutQuad);
    _anim_pos->setDuration(300);
    _anim_opa = new QPropertyAnimation(this, "opacity", this);
    _anim_opa->setEasingCurve(QEasingCurve::OutQuad);
    _anim_opa->setDuration(300);
    _anim_opa->setStartValue(0);
    _anim_opa->setEndValue(1);
    _icon_anim_opa = new QPropertyAnimation(this);
    _icon_anim_opa->setPropertyName("background");
    _icon_anim_opa->setEasingCurve(QEasingCurve::OutQuad);
    _icon_anim_opa->setDuration(300);
}

void ElMessage::startIconAnimation(bool isForward)
{
    _icon_anim_opa->setTargetObject(_icon);
    auto bgColor = _palette.color(ElPalette::Foreground);
    bgColor.setAlphaF(0);
    _icon_anim_opa->setStartValue(bgColor);
    bgColor.setAlphaF(1);
    _icon_anim_opa->setEndValue(bgColor);
    _icon_anim_opa->setDirection(isForward ? QAbstractAnimation::Forward
                                           : QAbstractAnimation::Backward);
    if (_icon_anim_opa->state() == QAbstractAnimation::Running)
        _icon_anim_opa->stop();
    _icon_anim_opa->start();
}

void ElMessage::refreshPosAnim(int start_pos_y, int end_pos_y)
{
    auto parent = dynamic_cast<QWidget *>(this->parent());
    auto center_x = (parent->width() - width()) / 2;
    _anim_pos->setStartValue(QPoint(center_x, start_pos_y));
    _anim_pos->setEndValue(QPoint(center_x, end_pos_y));
}

void ElMessage::createCloseBtn()
{
    _closeBtn = new ElTextButton("×", this);
    _closeBtn->setClipped(true);
    auto font = _closeBtn->font();
    font.setPointSize(12);
    _closeBtn->setFont(font);
    // change its style
    auto pal = _closeBtn->palette();
    pal.setColor(ElPalette::Normal, ElPalette::Foreground, ThemeColor::textDisabledColor);
    pal.setColor(ElPalette::Entered, ElPalette::Foreground, ThemeColor::infoColor);
    pal.setColor(ElPalette::Pressed, ElPalette::Foreground, ThemeColor::textPrimaryColor);
    _closeBtn->setPalette(pal);
    _closeBtn->hide();
    connect(_closeBtn, &ElTextButton::clicked, this, [=] {
        if (_is_repeat_click) // avoid click repeatly
            return;
        _is_repeat_click = !_is_repeat_click;
        _fadeTimer->stop();
        manager_handle_msgs_animation(); // called manually
    });
}
